﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections;

namespace Expand.ComponentModel
{
    public class TrackableList<T> : CollectionBase, IBindingList, IList<T>, IList, ICollection<T>, ICollection, IEnumerable, IRaiseItemChangedEvents
        where T : INotifyPropertyChanging, INotifyPropertyChanged, new()
    {
        public event ListChangedEventHandler ListChanged;

        public event ListChangingEventHandler ListChanging;

        public bool RaisesItemChangedEvents { get { return true; } }

        private PropertyChangingEventHandler ItemPropertyChangingHandler;
        private PropertyChangedEventHandler ItemPropertyChangedHandler;

        public TrackableList()
            : base()
        {
            ItemPropertyChangingHandler = new PropertyChangingEventHandler(ItemPropertyChanging);
            ItemPropertyChangedHandler = new PropertyChangedEventHandler(ItemPropertyChanged);
        }

        public TrackableList(int capacity)
            : base(capacity)
        {
            ItemPropertyChangingHandler = new PropertyChangingEventHandler(ItemPropertyChanging);
            ItemPropertyChangedHandler = new PropertyChangedEventHandler(ItemPropertyChanged);
        }

        protected override void OnClear()
        {
            OnListChanging(null, new ListChangingEventArgs(ListChangedType.Reset, -1));
            base.OnClear();
        }

        protected override void OnClearComplete()
        {
            base.OnClearComplete();
            OnListChanged(null, new ListChangedEventArgs(ListChangedType.Reset, -1));
        }

        protected override void OnInsert(int index, object value)
        {
            OnListChanging(value, new ListChangingEventArgs(ListChangedType.ItemAdded, index));
            base.OnInsert(index, value);
        }

        protected override void OnInsertComplete(int index, object value)
        {
            base.OnInsertComplete(index, value);
            OnListChanged(value, new ListChangedEventArgs(ListChangedType.ItemAdded, index));
        }

        protected override void OnSet(int index, object oldValue, object newValue)
        {
            OnListChanging(newValue, new ListChangingEventArgs(ListChangedType.ItemChanged, index));
            base.OnSet(index, oldValue, newValue);
        }

        protected override void OnSetComplete(int index, object oldValue, object newValue)
        {
            base.OnSetComplete(index, oldValue, newValue);
            OnListChanged(newValue, new ListChangedEventArgs(ListChangedType.ItemChanged, index));
        }

        protected override void OnRemove(int index, object value)
        {
            OnListChanging(value, new ListChangingEventArgs(ListChangedType.ItemDeleted, index));
            base.OnRemove(index, value);
        }

        protected override void OnRemoveComplete(int index, object value)
        {
            base.OnRemoveComplete(index, value);
            OnListChanged(value, new ListChangedEventArgs(ListChangedType.ItemDeleted, index));
        }

        protected void OnListChanged(object sender, ListChangedEventArgs e)
        {
            if (ListChanged != null) ListChanged(sender, e);
        }

        protected void OnListChanging(object sender, ListChangingEventArgs e)
        {
            if (ListChanging != null) ListChanging(sender, e);
        }

        private void ItemPropertyChanging(object sender, PropertyChangingEventArgs e)
        {
            OnListChanging(sender, new ListChangingEventArgs(ListChangedType.ItemChanged, IndexOf((T)sender), TypeDescriptor.GetProperties(sender)[e.PropertyName]));
        }

        private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            OnListChanged(sender, new ListChangedEventArgs(ListChangedType.ItemChanged, IndexOf((T)sender), TypeDescriptor.GetProperties(sender)[e.PropertyName]));
        }

        #region IBindingList
        public void AddIndex(PropertyDescriptor property)
        {
            throw new NotSupportedException();
        }

        public object AddNew()
        {
            T tmp = new T();
            Add(tmp);
            return tmp;
        }

        private bool _AllowEdit;
        public bool AllowEdit
        {
            get { return _AllowEdit; }
            set
            {
                if (value != AllowEdit)
                {
                    Clear();
                    _AllowEdit = value;
                }
            }
        }

        private bool _AllowNew;
        public bool AllowNew
        {
            get { return _AllowNew; }
            set
            {
                if (value != _AllowNew)
                {
                    Clear();
                    _AllowNew = value;
                }
            }
        }

        private bool _AllowRemove;
        public bool AllowRemove
        {
            get { return _AllowRemove; }
            set
            {
                if (value != _AllowRemove)
                {
                    Clear();
                    _AllowRemove = value;
                }
            }
        }

        public void ApplySort(PropertyDescriptor property, ListSortDirection direction)
        {
            throw new NotSupportedException();
        }

        public int Find(PropertyDescriptor property, object key)
        {
            throw new NotSupportedException();
        }

        public bool IsSorted { get { return false; } }

        public void RemoveIndex(PropertyDescriptor property)
        {
        }

        public void RemoveSort()
        {
            throw new NotSupportedException();
        }

        public ListSortDirection SortDirection
        {
            get { return ListSortDirection.Ascending; }
        }

        public PropertyDescriptor SortProperty
        {
            get { return null; }
        }

        public bool SupportsChangeNotification
        {
            get { return true; }
        }

        public bool SupportsSearching
        {
            get { return false; }
        }

        public bool SupportsSorting
        {
            get { return false; }
        }
        #endregion

        #region IList<T>
        public int IndexOf(T item)
        {
            return List.IndexOf(item);
        }

        public void Insert(int index, T item)
        {
            List.Insert(index, item);
            item.PropertyChanging += ItemPropertyChangingHandler;
            item.PropertyChanged += ItemPropertyChangedHandler;
        }

        public T this[int index]
        {
            get
            {
                return (T)List[index];
            }
            set
            {
                if (List[index] != ((object)value) && value != null)
                {
                    value.PropertyChanging += ItemPropertyChangingHandler;
                    value.PropertyChanged += ItemPropertyChangedHandler;
                    ((T)List[index]).PropertyChanging -= ItemPropertyChangingHandler;
                    ((T)List[index]).PropertyChanged -= ItemPropertyChangedHandler;
                }
                List[index] = value;
            }
        }

        public void Add(T item)
        {
            List.Add(item);
            item.PropertyChanging += ItemPropertyChangingHandler;
            item.PropertyChanged += ItemPropertyChangedHandler;
        }

        public bool Contains(T item)
        {
            return List.Contains(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            List.CopyTo(array, arrayIndex);
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Remove(T item)
        {
            try
            {
                int index = List.IndexOf(item);
                if (index >= 0)
                    List.Remove(item);
                else
                    return false;
            }
            catch (Exception)
            {
                return false;
            }
            item.PropertyChanging -= ItemPropertyChangingHandler;
            item.PropertyChanged -= ItemPropertyChangedHandler;
            return true;
        }
        #endregion

        public new IEnumerator<T> GetEnumerator()
        {
            return new TrackableListEnumerator(base.GetEnumerator());
        }

        public struct TrackableListEnumerator : IEnumerator<T>, IEnumerator
        {
            public IEnumerator BaseEnumerator;

            public TrackableListEnumerator(IEnumerator baseEnumerator)
            {
                BaseEnumerator = baseEnumerator;
            }

            public T Current
            {
                get { return (T)BaseEnumerator.Current; }
            }

            public void Dispose()
            {
                BaseEnumerator = null;
            }

            object IEnumerator.Current
            {
                get { return BaseEnumerator.Current; }
            }

            public bool MoveNext()
            {
                return BaseEnumerator.MoveNext();
            }

            public void Reset()
            {
                BaseEnumerator.Reset();
            }
        }
    }
}
